﻿using System;
using System.Diagnostics;
using System.IO.Compression;
using System.Net.Http;
using System.Reflection;
using System.Runtime.Loader;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;

namespace PasteCodeTaskBase
{
    public class TaskRemoteHostedService : IHostedService
    {
        private TaskChannelHelper _channelHelper;
        private ILogger<TaskRemoteHostedService> _logger;
        private IServiceProvider _serviceProvider;
        private IHttpClientFactory _httpclientFactory;
        private TaskNodeConfig _config;
        //private AssemblyLoadContext context;
        private JsonSerializerSettings setting;
        //private SemaphoreSlim _semaphore;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="channelHelper"></param>
        /// <param name="logger"></param>
        /// <param name="serviceProvider"></param>
        /// <param name="httpclientFactory"></param>
        /// <param name="config"></param>
        public TaskRemoteHostedService(TaskChannelHelper channelHelper,
            ILogger<TaskRemoteHostedService> logger,
            IServiceProvider serviceProvider,
            IHttpClientFactory httpclientFactory,
            IOptions<TaskNodeConfig> config)
        {
            _channelHelper = channelHelper;
            _logger = logger;
            _serviceProvider = serviceProvider;
            _httpclientFactory = httpclientFactory;
            _config = config.Value;
            //context = new AssemblyLoadContext();
            //_semaphore = new SemaphoreSlim(_config.MaxThreadCount);

            #region 配置序列号格式
            setting = new Newtonsoft.Json.JsonSerializerSettings();
            //日期格式化
            setting.DateFormatString = "yyyy-MM-dd HH:mm:ss";
            //是否格式化换行便于阅读
            //setting.Formatting = Newtonsoft.Json.Formatting.Indented;
            //首字母小写
            setting.ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver();
            #endregion
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public Task StartAsync(CancellationToken cancellationToken)
        {
            //Console.WriteLine($"{DateTime.Now} Remote Tick HostedService Start!");
            JoinMaster();
            ReadChannel();
            return Task.CompletedTask;
        }

        /// <summary>
        /// 检查任务
        /// </summary>
        private async void ReadChannel()
        {
            try
            {
                var _info = await _channelHelper.Task.Reader.ReadAsync();
                if (_info != null && _info != default)
                {
                    var info = _info;
                    var stop = new Stopwatch();
                    stop.Start();
                    PasteTaskCallBackModel? calldto = default;
                    try
                    {
                        //执行任务
                        //Console.WriteLine($"read task from channel {Newtonsoft.Json.JsonConvert.SerializeObject(info, setting)}");

                        if (!String.IsNullOrEmpty(info.AssemblyZip))
                        {
                            //加载外在的
                            _logger.LogInformation($"Load DLL Assembly from Url:{info.AssemblyZip}");
                            var path = $"/app/wwwroot/upload/{info.Id}/{info.FileVersion}";
                            if (!System.IO.Directory.Exists(path))
                            {
                                System.IO.Directory.CreateDirectory(path);
                            }
                            var savepath = $"{path}/{info.Assembly}";
                            var dllpath = $"/app/wwwroot/upload/{info.Id}/{info.FileVersion}/{info.Assembly}.dll";
                            var zippath = $"/app/wwwroot/upload/{info.Id}/{info.FileVersion}/{info.Assembly}.zip";
                            if (!System.IO.File.Exists(dllpath))
                            {
                                _logger.LogInformation($"Begin to download:{zippath} from url:{info.AssemblyZip}");
                                using var client = _httpclientFactory.CreateClient();
                                var array = await client.GetByteArrayAsync(info.AssemblyZip);
                                using var fs = new System.IO.FileStream(zippath, System.IO.FileMode.Create);
                                fs.Write(array, 0, array.Length);
                                fs.Close();
                                fs.Dispose();
                                ZipFile.ExtractToDirectory(zippath, savepath, true);
                                System.IO.File.Delete(zippath);
                            }

                            var assembly = AssemblyLoadContext.Default.LoadFromAssemblyName(new AssemblyName(System.IO.Path.GetFileNameWithoutExtension(dllpath)));
                            Type type = assembly.GetType(info.Assembly, true, true);
                            var task = Activator.CreateInstance(type) as IPasteCodeTaskBase;
                            if (task != null)
                            {
                                calldto = await task.Work(info);
                            }

                        }
                        else
                        {
                            //加载内置的
                            var _scope = _serviceProvider.CreateScope();
                            var tasks = _scope.ServiceProvider.GetServices<IPasteCodeTaskBase>();
                            foreach (var item in tasks)
                            {
                                var _t = item.GetType();
                                if (_t.Name == info.Assembly)
                                {
                                    calldto = await item.Work(info);
                                    break;
                                }
                            }
                        }
                        stop.Stop();
                        if (calldto != null && calldto != default)
                        {
                            calldto.logid = info.TaskLogId;
                            calldto.taskid = info.Id;
                            calldto.nodeid = info.NodeId;
                            calldto.duration = (int)stop.Elapsed.TotalSeconds;
                            //calldto.nodeid = 
                        }
                        else
                        {
                            calldto = new PasteTaskCallBackModel();
                            calldto.code = 500;
                            calldto.logid = info.TaskLogId;
                            calldto.taskid = info.Id;
                            calldto.message = $"{info.Assembly} Not Found";
                            calldto.duration = (int)stop.Elapsed.TotalSeconds;
                        }
                        await UpdateCallBack(calldto);
                    }
                    catch (Exception eload)
                    {
                        _logger.LogError(eload.Message);
                        //回调
                        stop.Stop();
                        calldto = new PasteTaskCallBackModel();
                        calldto.taskid = info.Id;
                        calldto.logid = info.TaskLogId;
                        calldto.nodeid = info.NodeId;
                        calldto.code = 500;
                        calldto.message = eload.Message;
                        calldto.duration = (int)stop.Elapsed.TotalSeconds;
                        await UpdateCallBack(calldto);
                    }
                }
            }
            catch (Exception exl)
            {
                _logger.LogError(exl.Message);
                await Task.Delay(100);
            }
            finally
            {
                ReadChannel();
            }
        }

        /// <summary>
        /// 任务回调 告知任务中心，任务执行的结果，默认为成功
        /// </summary>
        /// <param name="info"></param>
        /// <returns></returns>
        private async Task<bool> UpdateCallBack(PasteTaskCallBackModel info)
        {
            if (info != null)
            {
                if (!String.IsNullOrEmpty(_config.MasterUrl) && !String.IsNullOrEmpty(_config.JoinToken))
                {
                    try
                    {
                        var postdata = (JsonConvert.SerializeObject(info, setting));
                        using HttpContent httpcontent = new StringContent(postdata);
                        var timestmp = new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds();
                        var headtoken = ($"{timestmp}_0_{_config.JoinToken}".ToMd5Lower());
                        var xtoken = $"{timestmp}_0_{headtoken}";//服务端需要根据游戏ID获取 密钥，否则无法验证xtoken是否合法
                        httpcontent.Headers.Add("xtoken", xtoken);
                        httpcontent.Headers.Add("stoken", _config.PublicToken);
                        httpcontent.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json;charset=utf-8");
                        var client = _httpclientFactory.CreateClient();
                        var url = $"{_config.MasterUrl}api/task/callback";
                        var response = await client.PostAsync(url, httpcontent);
                        if (!response.IsSuccessStatusCode)
                        {
                            _logger.LogError($"Task CallBack Upload Error! Code:{response.StatusCode} Response:{await response.Content.ReadAsStringAsync()}");
                        }
                    }
                    catch (Exception exl)
                    {
                        _logger.LogWarning("task callback throw exception:");
                        _logger.LogError(exl.Message);
                    }
                }
                else
                {
                    _logger.LogError("配置文件错误 MasterUrl 或者 JoinToken 不能为空！");
                }
            }
            else
            {
                _logger.LogError("UpdateCallBack Query info is null!");
            }
            return true;
        }

        //加入主调度中心
        private void JoinMaster()
        {
            if (!String.IsNullOrEmpty(_config.MasterUrl) && !String.IsNullOrEmpty(_config.Url))
            {
                var timer = new System.Timers.Timer();
                timer.AutoReset = false;
                timer.Interval = 5000;
                timer.Elapsed += Timer_Elapsed;
                timer.Start();
            }
            else
            {
                _logger.LogWarning("if is client side check appsettings.json TaskNodeConfig!");
            }
        }

        /// <summary>
        /// 加入到调度中心
        /// </summary>
        private int trylinktime = 0;
        /// <summary>
        /// 是否加入到服务中了
        /// </summary>
        private bool _joined = false;
        private async void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            //Console.WriteLine($"{DateTime.Now} Timer_Elapsed ====== ------ -=-=-=-");
            var timer = (System.Timers.Timer)sender;
            if (!String.IsNullOrEmpty(_config.MasterUrl) && !String.IsNullOrEmpty(_config.Url))
            {
                if (String.IsNullOrEmpty(_config.JoinToken))
                {
                    _config.JoinToken = Guid.NewGuid().ToString().Replace("-", "").ToLower();
                }
                trylinktime++;
                if (trylinktime > 60)
                {
                    trylinktime = 60;
                }
                var join = new PasteTaskJoinInfo()
                {
                    Group = _config.Group,
                    JoinToken = _config.JoinToken,
                    Url = _config.Url,
                    ClientCode = _config.ClientCode
                };
                try
                {
                    _logger.LogWarning($"begin to join {_config.MasterUrl} timer master!");
                    var postdata = (Newtonsoft.Json.JsonConvert.SerializeObject(join, setting));
                    using HttpContent httpcontent = new StringContent(postdata);
                    var timestmp = new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds();
                    var headtoken = ($"{timestmp}_0_{_config.JoinToken}");
                    var xtoken = $"{timestmp}_0_{headtoken}";//服务端需要根据游戏ID获取 密钥，否则无法验证xtoken是否合法
                    httpcontent.Headers.Add("xtoken", xtoken);
                    httpcontent.Headers.Add("stoken", _config.PublicToken);
                    httpcontent.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json;charset=utf-8");
                    var client = _httpclientFactory.CreateClient();
                    var url = $"{_config.MasterUrl}api/task/join";
                    var response = await client.PostAsync(url, httpcontent);
                    if (!response.IsSuccessStatusCode)
                    {
                        timer.Interval = trylinktime * 5000;
                        timer.Start();
                    }
                    else
                    {
                        _joined = true;
                        _logger.LogWarning($"当前客户端:{_config.Url} 加入到任务主服务:{_config.MasterUrl} 成功! join:token:{_config.JoinToken}");
                        var one = Newtonsoft.Json.JsonConvert.DeserializeObject<PasteTaskJoinInfo>(await response.Content.ReadAsStringAsync());
                        if (one != null)
                        {
                            _config.JoinToken = one.JoinToken;
                        }
                    }
                }
                catch (Exception exl)
                {
                    _logger.LogError(exl.Message);
                    timer.Interval = trylinktime * 5000;
                    timer.Start();
                }
            }
            else
            {
                _logger.LogError($"TaskNodeConfig 配置错误！ JoinToken:{_config.JoinToken} MasterUrl:{_config.MasterUrl}!");
                //timer.Interval = 5000;
                //timer.Start();
                timer.Stop();
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public async Task StopAsync(CancellationToken cancellationToken)
        {
            //离开集群... .. .
            if (_joined)
            {
                if (!String.IsNullOrEmpty(_config.MasterUrl) && !String.IsNullOrEmpty(_config.JoinToken))
                {
                    var join = new PasteTaskJoinInfo()
                    {
                        Group = _config.Group,
                        JoinToken = _config.JoinToken,
                        Url = _config.Url
                    };
                    try
                    {
                        var postdata = (Newtonsoft.Json.JsonConvert.SerializeObject(join, setting));
                        using HttpContent httpcontent = new StringContent(postdata);
                        var timestmp = new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds();
                        var headtoken = ($"{timestmp}_0_{_config.JoinToken}");
                        var xtoken = $"{timestmp}_0_{headtoken}";//服务端需要根据游戏ID获取 密钥，否则无法验证xtoken是否合法
                        httpcontent.Headers.Add("xtoken", xtoken);
                        httpcontent.Headers.Add("stoken", _config.PublicToken);
                        httpcontent.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json;charset=utf-8");
                        var client = _httpclientFactory.CreateClient();
                        var url = $"{_config.MasterUrl}api/task/leave";
                        var response = await client.PostAsync(url, httpcontent);
                    }
                    catch (Exception exl)
                    {
                        _logger.LogError(exl.Message);
                    }
                }
            }
        }
    }
}
